package com.hyt.it.ogt.ks.websocket;

import com.alibaba.fastjson.JSON;
import com.hyt.it.ogt.ks.config.ConfigManager;
import com.hyt.it.ogt.ks.enums.CandidateStatusCode;
import com.hyt.it.ogt.ks.enums.MessageTypeCode;
import com.hyt.it.ogt.ks.feign.KwClient;
import com.hyt.it.ogt.ks.service.ICandidateProcessService;
import com.hyt.it.ogt.ks.service.ILogService;
import com.hyt.it.ogt.ks.util.ComputerUtil;
import com.hyt.it.ogt.ks.util.KeyDefineCommon;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.websocket.Session;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * @author 刘恒活
 * @since 2021/3/17 10:50
 */
@Component
@Slf4j
public class CandidateWebSocketService {
    private static final String SEPARATOR = "_";
    public static final String EXAM_WEBSOCKET = "KS_WEBSOCKET";
    public static final String WEBSOCKET_NORMAL_KEY="WEBSOCKET_NORMAL_KEY:";
    private static Map<String, Session> onlineSession = new ConcurrentHashMap<>();
    private static Map<String, String> map = new HashMap<>();
    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private ICandidateProcessService iCandidateProcessService;
    @Resource
    private ILogService iLogService;
    @Resource
    private ConfigManager configManager;
    @Resource
    private KwClient kwClient;

    /**
     * websocket连接上
     *
     * @param session
     * @param examId
     * @param candidateId
     */
    public void addSession(Session session, String examId, String candidateId) {
        String sessionId = session.getId();
        onlineSession.put(sessionId, session);
        this.setCache(sessionId, examId, candidateId);
    }

    /**
     * websocket断开
     *
     * @param session
     */
    public void removeSession(Session session) {
        String currentSessionId = session.getId();
        String values = this.getCache(currentSessionId);
        if (StringUtils.isNotBlank(values)) {
            String[] arr = values.split(SEPARATOR);
            if (arr.length >= 2) {
                String examId = arr[arr.length - 2];
                String candidateId = arr[arr.length - 1];
                // 删除移动端设备缓存
                redisTemplate.delete(KeyDefineCommon.getCollectMobileDeviceParamKey(examId, candidateId));
                // 不管当前属于哪个回话，触发onclose都删除
                redisTemplate.delete(KeyDefineCommon.getWebsocketKey(currentSessionId));
                onlineSession.remove(currentSessionId);
                log.info("websocket连接关闭调用:{}、{}", values, currentSessionId);
                try {
                    session.close();
                } catch (IOException e) {
                    log.error("", e);
                }
                Object lastSessionId = redisTemplate.opsForValue().get(this.getKey(examId, candidateId));
                if(lastSessionId != null && currentSessionId.equalsIgnoreCase(lastSessionId.toString())) {
                    // 只有触发事件的session和缓存最好存储的session一致才允许修改状态
                    iCandidateProcessService.updateCandidateOnlineExamState(examId, candidateId, false, CandidateStatusCode.INTERRUPT.getCode());
                    // 可能存在删除报错
                    redisTemplate.delete(KeyDefineCommon.getWebsocketKey(lastSessionId.toString()));
                    redisTemplate.delete(this.getKey(examId, candidateId));
                    onlineSession.remove(lastSessionId.toString());
                }
            }
        }
    }

    /**
     * 考中发消息
     * @param json
     */
    public void sendMessage(String json) {
        WebsocketSendMessageDTO messageDTO = JSON.parseObject(json, WebsocketSendMessageDTO.class);
        Object sessionId = redisTemplate.opsForValue().get(this.getKey(messageDTO.getExamId(), messageDTO.getCandidateId()));
        if(sessionId != null) {
            Session session = onlineSession.get(sessionId.toString());
            if (session != null) {
                try {
                    session.getBasicRemote().sendText(JSON.toJSONString(messageDTO.getMessageContent()));
                } catch (IOException e) {
                    log.error("", e);
                }
                // 消息命中服务，通知考务更改消息状态
                log.info("发送消息成功:{}", JSON.toJSONString(messageDTO));
                kwClient.feedbackWebsocketMessageResult(messageDTO.getId(), ComputerUtil.getServerIp());
            }
        }
    }

    /**
     * 接收消息
     *
     * @param message {"type":"1-修改为考中状态","examId":"xxx","candidateId":"xxx"}
     * @param session
     */
    public void onMessage(String message, Session session) {
        String currentSessionId = session.getId();
        log.info("=====收到前端发送的消息内容：{}、{}", message, currentSessionId);
        String val = this.getCache(currentSessionId);
        if (StringUtils.isNotBlank(val)) {
            String[] arr = val.split(SEPARATOR);
            String examId = arr[arr.length - 2];
            String candidateId = arr[arr.length - 1];
            boolean flag = "check".equalsIgnoreCase(message);
            try {
                //redis记录当前考生心跳时间
                redisTemplate.opsForValue().set(StringUtils.joinWith(SEPARATOR,WEBSOCKET_NORMAL_KEY,examId,candidateId),StringUtils.joinWith(SEPARATOR,examId,candidateId, LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli()),1, TimeUnit.HOURS);
                if (flag) {
                    // 心跳检测
                    session.getBasicRemote().sendText(message);
                } else {
                    Object lastSessionId = redisTemplate.opsForValue().get(this.getKey(examId, candidateId));
                    if(null!=lastSessionId&&currentSessionId.equalsIgnoreCase(lastSessionId.toString())) {
                        WebsocketReceiveMessageDTO vo = JSON.parseObject(message, WebsocketReceiveMessageDTO.class);
                        if (MessageTypeCode.STATE.getCode().equalsIgnoreCase(vo.getType())) {
                            // 默认都是成功，不存在失败可能，失败是返回给前端一个
                            iCandidateProcessService.updateCandidateOnlineExamState(vo.getExamId(), vo.getCandidateId(), true, CandidateStatusCode.TESTING.getCode());
                        } else {
                            flag = true;
                        }
                    }
                }
            } catch (Exception e) {
                log.error("接收websocket消息后处理业务异常：", e);
                if (!flag) {
                    MessageContentDTO dto = new MessageContentDTO();
                    dto.setType(MessageTypeCode.STATE.getCode());
                    dto.setSendAgainAfterSecond(configManager.getSendAgainAfterSecond());
                    try {
                        session.getBasicRemote().sendText(JSON.toJSONString(dto));
                    } catch (IOException ex) {
                        log.error("", e);
                    }
                }
            }
        }
    }

    public void onError(Session session, Throwable error) {
        log.error("", session.getId(), error);
    }

    /**
     * 设置缓存
     *
     * @param sessionId
     * @param examId
     * @param candidateId
     */
    private void setCache(String sessionId, String examId, String candidateId) {
        redisTemplate.opsForValue().set(KeyDefineCommon.getWebsocketKey(sessionId), this.getKey(examId, candidateId), 1, TimeUnit.DAYS);
        redisTemplate.opsForValue().set(this.getKey(examId, candidateId), sessionId, 1, TimeUnit.DAYS);
    }

    /**
     * 获取缓存
     *
     * @param sessionId
     * @return
     */
    private String getCache(String sessionId) {
        String key = KeyDefineCommon.getWebsocketKey(sessionId);
        return (String) redisTemplate.opsForValue().get(key);
    }

    private String getKey(String examId, String candidateId) {
        return StringUtils.joinWith(SEPARATOR, EXAM_WEBSOCKET, examId, candidateId);
    }

    public Map<String, Session> getOnlineSession() {
        return onlineSession;
    }

}
